#  This file is part of OpenLightingDesigner.
# 
#  OpenLightingDesigner is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  OpenLightingDesigner is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenLightingDesigner.  If not, see <http://www.gnu.org/licenses/>.
#
# sequencer.py
# Copyright (c) 2010 - 2011 Matthew Reid

import sys
import datetime
import threading
from Bases import BaseObject, BaseThread, setID, MasterClock

LOOP_MODES = ('single', 'updn', 'up')

class Sequencer(BaseObject):
    _saved_class_name = 'Sequencer'
    _saved_attributes = ['id', 'name', 'persistent', 'duration', 'mode', 'controllers_id']
    _Properties = {'seconds_prop':dict(default=None, type=float, min=0., max=2., quiet=True), 
                   'state':dict(default=False, quiet=True), 
                   'duration':dict(default=2., min=0., max=1024., fformat='_format_duration'), 
                   'mode':dict(default='single')}
    def __init__(self, **kwargs):
        kwargs['ParentEmissionThread'] = SequencerWorker(sequencer=self)
        super(Sequencer, self).__init__(**kwargs)
        self.register_signal('state_changed')
        self.seconds = None
        #self._state = False
        self.fps = float(kwargs.get('fps', 24))
        self.interval =  self.fps / 1000
        if 'deserialize' not in kwargs:
            self.id = setID(kwargs.get('id'))
            self.name = kwargs.get('name', '')
            self.persistent = kwargs.get('persistent', False)
            self.duration = kwargs.get('duration', 2.)
            self.mode = kwargs.get('mode', 'single')
            self.controllers_id = set()
        self.clock = kwargs.get('clock')
        if not self.clock:
            self.clock = MasterClock(interval=self.interval)#, callbacks=[self.on_tick])
        self.stopping = False
        self.stopped = threading.Event()
        self.controllers = {}
        self.controller_set = set()
        self.controllers_to_remove = set()
        #self.start_time = self.clock.seconds
        self.bind(state=self._on_state_set, 
                  duration=self._on_duration_set)
                  
    def add_controller(self, controller):
        controller.ParentEmissionThread = self.ParentEmissionThread
        self.controllers.update({controller.id:controller})
        self.controller_set.add(controller)
    def del_controller(self, controller):
        self.controllers_to_remove.add(controller)
        if not self.state:
            self.remove_controllers()
        #self.controller_set.discard(controller)
        #if controller.id in self.controllers:
        #    del self.controllers[controller.id]
    
    def _on_state_set(self, **kwargs):
        value = kwargs.get('value')
        self.emit('state_changed', obj=self, id=self.id, state=value)
        
    def _on_duration_set(self, **kwargs):
        value = kwargs.get('value')
        self.Properties['seconds_prop'].max = value
        
    @property
    def scaled_time(self):
        #t = (self.seconds - self.start_time) / self.duration
        t = self.seconds / self.duration
        if t > 1:
            t = 1.
        elif t < 0:
            t = 0.
        #print 'seq: csec=%s, start=%s, duration=%s, t=%s' % (self.clock.seconds, self.start_time, self.duration, t)
        if self.direction:
            return t
        return (t * -1.0) + 1.0
        
    def _format_duration(self, value):
        return float(value)
        
    def start(self, direction=True):
        self.direction = direction
        self.start_time = None
        self.seconds = None
        self.seconds_prop = 0.
        self.stopping = False
        self.stopped.clear()
        #controllers = self.controllers
        #keys = controllers.keys()
        #d = dict(zip(keys, [repr(controllers[key]) for key in keys]))
        #print 'CLOCK CONTROLLERS: %r' % (self.controllers)
        #ms = str(self.clock.interval / 1000.)
        #self.clock_granularity = len(ms.split('.')[1])
        #print 'granularity: ', self.clock_granularity
        self.pause_garbage_collection()
        self.clock.connect('tick', self.on_tick)
#        t = getattr(self, 'ParentEmissionThread', None)
#        if t is not None:
#            self.clock.add_callback(t.on_tick)
#        else:
#            self.clock.add_callback(self.on_tick, threaded=True)
        
        #print 'start'
        self.state = True
        if not self.clock.running:
            self.clock.start()
            
    def stop(self, **kwargs):
        blocking = kwargs.get('blocking', False)
        self.stopping = True
        t = getattr(self, 'ParentEmissionThread', None)
        if t is not None:
            cb = t.on_tick
        else:
            cb = self.on_tick
        #self.clock.del_callback(cb, blocking=blocking)
        self.clock.disconnect(callback=self.on_tick)
        self.resume_garbage_collection()
        self.controllers_to_remove |= self.controller_set
        self.remove_controllers()
        self.stopped.set()
        self.state = False
        
    def unlink(self):
        self.stop(blocking=True)
        t = self.ParentEmissionThread
        if t is not None:
            t._threaded_calls_idle.wait()
        self.stop_ParentEmissionThread()
        super(Sequencer, self).unlink()
        #print self, 'unlinked'
        
    #def on_tick(self, clock, seconds):
    def on_tick(self, **kwargs):
        clock = kwargs.get('clock')
        seconds = kwargs.get('seconds')
        #print 'seq: ', seconds
        if self.stopping:
            self.remove_controllers()
            return
        #_exec_start = datetime.datetime.now()
        if self.start_time is None:
            self.start_time = seconds #round(seconds, self.clock_granularity)
            #print 'start time: %s, realclocktime: %s' % (self.start_time, seconds)
        t = round(seconds - self.start_time, 2)
        #print t
        self.seconds = t
        scaledT = self.scaled_time
        #print 't=%s, scaled=%s, real=%s' % (t, scaledT, seconds)
        #print '%s, %s, %.10f    %.10f' % (seconds, self.clock.seconds, t, scaledT)
        self.remove_controllers()
        try:
            for c in self.controller_set:
                c.on_interval(scaledT)
        except:
            self.LOG.warning(sys.exc_info())
        self.remove_controllers()
        self.seconds_prop = t
        if t >= self.duration:
            self.on_duration_end()
        #_exec_end = datetime.datetime.now()
        #_exec_time = _exec_end - _exec_start
        #if _exec_time.microseconds > 030000:
        #    print t, ' sq exec time: ', _exec_time
        
            
    def remove_controllers(self):
        #if len(self.controllers_to_remove):
        #    print 'removing %s controllers at %s' % (len(self.controllers_to_remove), self.seconds)
        for c in self.controllers_to_remove:
            self.controller_set.discard(c)
            if c.id in self.controllers:
                del self.controllers[c.id]
        self.controllers_to_remove.clear()
        
    def on_duration_end(self):
        if self.mode == 'single':
            self.stop()
        elif self.mode == 'updn':
            self.start(direction=not self.direction)
        elif self.mode == 'up':
            self.start()

class SequencerWorker(BaseThread):
    def __init__(self, **kwargs):
        self.sequencer = kwargs.get('sequencer')
        kwargs['thread_id'] = 'SequencerWorker(%s)' % (id(self.sequencer))
        super(SequencerWorker, self).__init__(**kwargs)
    def on_tick(self, clock, seconds):
        self.insert_threaded_call(self.sequencer.on_tick, clock, seconds)
